package fun.ipconfig;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

@Intercepts({

        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class })
})
@Component
public class MybatisExecutorPluginAudit implements Interceptor {
    Logger logger = LoggerFactory.getLogger(MybatisExecutorPluginAudit.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 只调试那些包 针对调试
        String[] filter_package = {
                ""
        };
        // 返回list 限制最大打印20行
        int max_result_row = 20;
        String method = ((MappedStatement) invocation.getArgs()[0]).getId();
        BoundSql boundSql = ((MappedStatement) invocation.getArgs()[0]).getBoundSql(invocation.getArgs()[1]);

        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        start = System.currentTimeMillis() - start;

        StringBuilder builder = new StringBuilder();
        ObjectMapper mapper = new ObjectMapper();
        if(result instanceof List){
            builder.append("返回条数: ").append(((List)result).size()) .append("\n");
            int i = 0;
            for(Object f: (List)result){
                i++;
                if(i>max_result_row){
                    break;
                }
                builder.append(mapper.writeValueAsString(f)).append("\n");
            }
        }else{
            builder.append("返回条数：");
            builder.append(result==null?"0":"1");
            builder.append("\n");
            builder.append(mapper.writeValueAsString(result));
            builder.append("\n");
        }
        // 如果 filter_package 指定了包则只打印对应的mybatis日志
        boolean filter = false;
        for(String pkg : filter_package){
            if(method.startsWith(pkg)){
                filter = true;
            }
        }

        String sql = getSql(boundSql)
                .replaceFirst("select ","select\n\t\t")
                .replaceFirst(" from ","\n\tfrom\n\t\t")
                .replace(" left join ","\n\tleft join\n\t\t")
                .replace(" on ","\n\ton\n\t\t")
                .replace(" where ","\n\twhere\n\t\t")
                .replaceFirst("SELECT ","\n\tselect\n\t\t")
                .replaceFirst(" FROM ","\n\tfrom\n\t\t")
                .replace(" LEFT JOIN ","\n\tleft join\n\t\t")
                .replace(" ON ","\n\ton\n\t\t")
                .replace("WHERE","\n\twhere\n\t\t")
                .replaceFirst("insert ","insert\n\t\t")
                .replace(" set ","\n\tset\n\t\t")
                .replaceFirst("update ", "update\n\t\t")
                .replaceFirst("delete ","delete\n\t\t")
                .replaceFirst("INSERT ","insert\n\t\t")
                .replace(" SET ","\n\tset\n\t\t")
                .replaceFirst("UPDATE ", "update\n\t\t")
                .replaceFirst("DELETE ","delete\n\t\t")
                .replace("values ","\n\tvalues\n\t\t")
                .replace("VALUES ","\n\tvalues\n\t\t")
                ;
        if( filter_package.length < 1 || filter) {
            if(start>1000){
                logger.info("\033[31m耗时：{} \n\tcall:\n\t\t{}\n\t{}\n{}\033[0m",
                        start, method, sql, builder);
            }else {
                logger.info("耗时：{} \n\tcall:\n\t\t{}\n\t\033[32m{}\033[0m \n{} ",
                        start, method, sql, builder);
            }
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 如果是执行的方法则调用当前对象进行解析
        if (target instanceof Executor)
            return Plugin.wrap(target, this);
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * @author gtinlian
     * @param boundSql
     * @return
     * @Description 获取sql，如果sql "参数"中携带 <?> 则将参数个sql 拼接，如果sql"参数"中不携带 <?> 则完整sql拼接
     *
     */
    String getSql(BoundSql boundSql) {
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        // 获取参数
        List<Object> param = getParameter(boundSql);
        // 渲染参数
        return preparedStatementImpl(sql,param);
    }

    /**
     * @author gtinlian
     * @param boundSql
     * @return
     * @Description sql获取参数列表
     * @author gtinlian
     */
    private List<Object> getParameter(BoundSql boundSql) {
        List<Object> result = new ArrayList<>();
        List<ParameterMapping> mappings = boundSql.getParameterMappings();
        for (ParameterMapping mapping : mappings) {
            // 字段映射实体名
            String propertyName = mapping.getProperty();

            Object value = boundSql.getAdditionalParameter(propertyName);
            Object paramObject = boundSql.getParameterObject();
            try {
                // 如果是单参数没有声明标量时 ParamObject 存储一个值 
                // ParamObject 的数据对象模式   
                // 1： [String] 单个值变量     
                // 2： Map<mapperProperty,value>  储存值或对象   
                // 3: Map<mapperProperty.split("[.]")[0],Entity>
                if (!(paramObject instanceof HashMap)) {
                    if(propertyName != null){
                        try {
                            Method f =paramObject.getClass().getMethod("get"+propertyName.substring(0, 1).toUpperCase()+propertyName.substring(1));
                            f.setAccessible(true);
                            value = f.invoke(paramObject);
                        }catch (Exception e){
                            value = paramObject;
                        }
                    }else {
                        value = paramObject;
                    }
                } else {
                    value = value == null ? ((Map) paramObject).get(propertyName) : value;
                }
            } catch (Exception e) {
                try {
                    String[] param = propertyName.split("[.]");
                    String fileName = null;
                    Object paramObj = paramObject;
                    if (param.length > 1) {
                        for (int i = 0; i < param.length - 1; i++) {
                            try {
                                paramObj = ((Map) paramObj).get(param[i]);
                            } catch (Exception er) {
                                Field field = paramObj.getClass().getDeclaredField(param[i]);
                                field.setAccessible(true);
                                paramObj = field.get(paramObj);
                            }
                        }
                        fileName = param[param.length - 1];
                    }
                    Field field = paramObj.getClass().getDeclaredField(fileName);
                    field.setAccessible(true);
                    value = field.get(paramObj);
                } catch (Exception exception) {
                }
            }
            result.add(value);
        }
        return result;
    }

    /**
     * @author gtinlian
     * 渲染sql 方法
     * @param sql PreparedStatement格式sql
     * @param arr 参数
     * @return
     */
    static String preparedStatementImpl(String sql,List<Object> arr){
        return preparedStatementImpl(sql,arr.toArray());
    }
    static String preparedStatementImpl(String sql,Object ...array) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        StringBuilder buffer = new StringBuilder();
        // 参数下表
        int i = 0;
        // 是否被标记未字符串
        boolean ingres = false;
        // 字符串结束标识
        char end = '0';
        // 处理转义字符
        boolean convert = false;
        for (char c : sql.toCharArray()) {
            // 处理转义字符
            if (convert) {
                buffer.append(c);
                convert = false;
                continue;
            }
            if (c == '\\') {
                convert = true;
                buffer.append(c);
                continue;
            }
            // 处理屏蔽字符
            if (!ingres && (c == '"' || c == '\'')) {
                end = c;
                ingres = true;
                buffer.append(c);
                continue;
            } else if (ingres) {
                if (c == end) {
                    ingres = false;
                    buffer.append(c);
                    continue;
                }
                buffer.append(c);
                continue;
            }
            // 处理参数
            if (c == '?') {
                if (array[i] instanceof String) {
                    buffer.append("\'");
                    buffer.append(((String) array[i]).replaceAll("\"","\\\"").replaceAll("\'","\\\'"));
                    buffer.append("\'");
                } else if(array[i] instanceof Date){
                    buffer.append("'").append(format.format(array[i])).append("'");
                }else{
                    buffer.append(array[i]);
                }
                i++;
            } else {
                buffer.append(c);
            }
        }
        return buffer.toString();
    }
}